package Grantsfire::Controller::Async;

use strict;
use base 'Catalyst::Base';

use HTTP::Date;
use DateTime;
use Text::Microformat;
use URI;
use DateTime;
use URI::Fetch;
use Grantsfire::Geocoder;

sub update : Local {
    my ($self, $c) = @_;
    _poll_feeds($c);
    $c->res->body('<ok/>');
    $c->redirect_success("Feeds scanned.");
}

sub _stringify {
    my $s = shift;
    return $s if !defined $s;
    return $s->body if ref $s eq 'XML::Feed::Content';
    return "$s";
}

sub _poll_feeds {
    my $c = shift;
    my $rs = $c->model('GF::Feed')->search;    
    while (my $feed_rec = $rs->next) {
        scan_feed($c, $feed_rec);
    }
}

sub _create_tags {
    my $c = shift;
    my $entry = shift;
    foreach my $tag (@_) {
        $tag = lc $tag;
        $tag =~ s/[^A-Za-z0-9 ]//g;
        $tag =~ s/(^ +| +$)//g;
        $c->model('GF::Tag')->find_or_create({entry => $entry->id, tag => $tag});
    }
}

sub scan_feed {
    my $c = shift;
    my $feed_rec = shift;
    my $res = URI::Fetch->fetch($feed_rec->url);
	if (!$res) {
		$c->log->error("Fetch of ".$feed_rec->url." failed (".URI::Fetch->errstr.")");
        $c->model('GF::FeedEvent')->new({feed=>$feed_rec->id, event=>'fetch failed'})->insert;
	}
	else {
		my @things = eval {Text::Microformat->new($res->content, content_type => $res->content_type)->find};
	    if (@things and $things[0]) {
	        _store_hgrants($c, $feed_rec, @things);
	    }
	    else {
	        $c->log->error("Fetch of ".$feed_rec->url." failed ($@)");
	        $c->model('GF::FeedEvent')->new({feed=>$feed_rec->id, event=>'fetch failed'})->insert;
	    }
	}
}

sub _gen_hgrant_id {
    my $feed_rec = shift;
    my $hgrant = shift;
    # TODO hgrant parser needs to be fixed for this to work - it mistakes hcard urls for hgrant urls
    return 'org' . $feed_rec->org->id . '-' . _stringify($hgrant->Get('id')) if defined $hgrant->Get('id'); 
    my $id = $feed_rec->url->clone;
    my $fragment = ($hgrant->Get('grantor') ? ($hgrant->Get('grantor.org') || $hgrant->Get('grantor.fn')) : '') 
                    . " " . ($hgrant->Get('title') || '') . " " . ($hgrant->Get('description') || '');
    $fragment = lc $fragment;
    $fragment =~ s/\W+/-/g;
    $fragment = substr $fragment, 0, 128;
    $id->fragment($fragment);
    return $id;
}

sub _not_modified_since {
    my ($c, $rec, $last_modified) = @_;
    my $rec_modified = $rec->modified;
    my $not_modified = 0;
    if ($last_modified and $rec_modified) {
        $last_modified->set_time_zone('UTC');
        eval {$not_modified = DateTime->compare($rec_modified, $last_modified) >= 0 ? 1 : 0};
        if ($@) {
            $c->log->dumper([$rec_modified, $last_modified]);
            die ($@);
        }
    }
    $c->log->debug("$rec " . $rec->url . ' not modified since ' . $last_modified) if $not_modified;
    return $not_modified;
}


sub _store_hgrants {
    my $c = shift;
    my $feed_rec = shift;

    my $i = 0;
    my @geo_lookup_queue; # grants with geo-info
	$c->log->debug("found ".scalar(@_)." grants");
    foreach my $hgrant (@_) {
        next unless $hgrant->isa('Text::Microformat::Element::hGrant');

		$c->log->debug("store grant: ", _gen_hgrant_id($feed_rec, $hgrant) 
		    . ", id=" . (defined $hgrant->Get('id') ? $hgrant->Get('id') : ""));
        my $entry_rec = $c->model('GF::Entry')->find_or_create({uuid => _gen_hgrant_id($feed_rec, $hgrant), feed => $feed_rec->id});

        $entry_rec->title($hgrant->Get('title')); # X
        $entry_rec->url($hgrant->GetM('url')); # X
        $entry_rec->description(_stringify($hgrant->Get('description'))); # X
        
        foreach my $dt (qw/dtstart dtend/) { # X
            my $epoch = str2time($hgrant->GetM("period.$dt"));
            $entry_rec->$dt(DateTime->from_epoch(epoch => $epoch)) if $epoch;
        }

        $entry_rec->currency(_stringify($hgrant->GetM('amount.currency'))); # X
        $entry_rec->amount(_stringify($hgrant->GetM('amount.amount'))); # X

		# read in tags
		foreach my $grant_tag (@{$hgrant->tags}) { # X
			# create tag if it doesn't exist
			my $tag = $c->model('GF::Tag')->find_or_create({ tag => $grant_tag->HumanValue });
			
			# add mapping to tag
			$entry_rec->add_to_tags($tag);
		}

		# generate/update vcards
        foreach my $role (qw/grantor grantee grant/) {
			# determine hgrant section we are interested in
			my $role_hgrant_class = $role eq 'grant' ? 'geo-focus' : $role;	
			
            # determine name of relation to vcard
            my $role_rel = $role eq 'grant' ? 'grant_vcard' : $role;

			# add link from entry to vcard, creating the vcard if it doesn't exist
            my $vcard = $entry_rec->$role_rel || do {	
                my $linked_vcard;
				if ($role ne 'grant') {
                    # if grantor or grantee, use existing vcard if present
                    my %search_params;
                    my $match = 0;

                    # if some properties of the grantor/grantee exist and match a vcard, use it
                    foreach my $prop (qw/tel email url adr_street_address adr_locality adr_region adr_postal_code/) {
                        my $val = _stringify($hgrant->Get("$role_hgrant_class.$prop"));
                        next unless $val;
                        $search_params{$prop} = $val;
                        $match++;
                    }

                    if ($match >= 3) {
                        $linked_vcard = $c->model('GF::Vcard')->find_or_create(\%search_params);
                    } else {
                        $linked_vcard = $c->model('GF::Vcard')->create({});
                    }
				} else {
					# make grant vcard
					$linked_vcard = $c->model('GF::Vcard')->create({});
				}

                $entry_rec->$role_rel($linked_vcard); 
                $linked_vcard;
            };

            my $org = $role eq 'grantor' ? $feed_rec->org->vcard->org : undef;

            if (defined $hgrant->Get("$role_hgrant_class.org") or defined $hgrant->Get("$role_hgrant_class.fn")) {
                $org = _stringify($hgrant->Get("$role_hgrant_class.org") || $hgrant->Get("$role_hgrant_class.fn"));
            }
			
			if ($role eq 'grant') {
				# geo-focus location info is stored differently in hGrant
            	$vcard->adr_locality(_stringify($hgrant->Get("$role_hgrant_class.locality")));
            	$vcard->adr_region(_stringify($hgrant->Get("$role_hgrant_class.region")));
            	$vcard->adr_postal_code(_stringify($hgrant->Get("$role_hgrant_class.postal_code")));
            	$vcard->adr_country_name(_stringify($hgrant->Get("$role_hgrant_class.country")));

                # attempt to geocode this address. issue a warning if it is invalid.
                push @geo_lookup_queue, $vcard if Grantsfire::Geocoder->can_geocode($vcard);
			} else {
            	$vcard->adr_post_office_box(_stringify($hgrant->Get("$role_hgrant_class.adr.post_office_box")));
            	$vcard->adr_extended_address(_stringify($hgrant->Get("$role_hgrant_class.adr.extended_address")));
            	$vcard->adr_street_address(_stringify($hgrant->Get("$role_hgrant_class.adr.street_address")));
            	$vcard->adr_locality(_stringify($hgrant->Get("$role_hgrant_class.adr.locality")));
            	$vcard->adr_region(_stringify($hgrant->Get("$role_hgrant_class.adr.region")));
            	$vcard->adr_postal_code(_stringify($hgrant->Get("$role_hgrant_class.adr.postal_code")));
            	$vcard->adr_country_name(_stringify($hgrant->Get("$role_hgrant_class.adr.country_name")));
			}
			
			$vcard->tel(_stringify($hgrant->Get("$role_hgrant_class.tel")));
			$vcard->email(_stringify($hgrant->Get("$role_hgrant_class.email")));
			$vcard->url(_stringify($hgrant->GetM("$role_hgrant_class.url")));
			
            $vcard->org($org);
            $vcard->fn($org);
	
            $vcard->update;
        }

        $entry_rec->update;
    }

    # attempt to geocode grants with geo-info
    my $geocodings = Grantsfire::Geocoder->geocode_vcards($c, @geo_lookup_queue);
    my %failures;

    foreach my $vcard (@geo_lookup_queue) {
        # lookup successful?
        next if $geocodings->{$vcard->id};

        $failures{$vcard->loc_string}++;
        $c->log->warn('Could not geocode "' . $vcard->loc_string . '"');
    }

    $c->stash->{geocode_failures} = \%failures;
}
1;
